﻿using UnityEngine.Assertions;

namespace System.Collections.Generic {

    public interface IDictionaryDataUpdated<TKey, TValue> : IChangable {

        public delegate void UpdatedDelegate(TKey key, TValue oldValue, TValue newValue);

        event UpdatedDelegate Updated;
    }

    public interface IDictionaryDataAdded<TKey, TValue> : IChangable {

        public delegate void AddedDelegate(TKey key, TValue newValue);

        event AddedDelegate Added;
    }

    public interface IDictionaryDataRemoved<TKey, TValue> : IChangable {

        public delegate void RemovedDelegate(TKey key, TValue oldValue);

        event RemovedDelegate Removed;
    }

    public interface IDictionaryDataCleared<TKey, TValue> : IChangable {

        public delegate void ClearedDelegate();

        event ClearedDelegate Cleared;
    }

    public interface IDictionaryDataEventDispatcher<TKey, TValue> : IDictionaryDataUpdated<TKey, TValue>, IDictionaryDataAdded<TKey, TValue>, IDictionaryDataRemoved<TKey, TValue>, IDictionaryDataCleared<TKey, TValue> {
    }

    public interface IReadOnlyDictionaryData<TKey, TValue> : IDictionaryDataEventDispatcher<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> {

        TValue GetValue(TKey key);

        IDictionaryData<TKey, TValue> AsReadWrite();
    }

    public interface IFixedLengthDictionaryData<TKey, TValue> : IDictionaryDataUpdated<TKey, TValue> {

        void Update(TKey key, TValue value);

        void Update(TKey key, Action<TValue> updater);
    }

    public interface IDictionaryData<TKey, TValue> : IDictionaryDataEventDispatcher<TKey, TValue>,
        IReadOnlyDictionaryData<TKey, TValue>, IFixedLengthDictionaryData<TKey, TValue>,
        IDictionaryDataAdded<TKey, TValue>, IDictionaryDataRemoved<TKey, TValue>, IDictionaryDataCleared<TKey, TValue>,
        IDictionaryDataUpdated<TKey, TValue>, IChangable, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable {

        bool TryAdd(TKey key, TValue value);

        bool TryAdd(KeyValuePair<TKey, TValue> item);

        new int Count { get; }

        new ICollection<TKey> Keys { get; }

        new ICollection<TValue> Values { get; }

        new TValue this[TKey key] { get; set; }

        new bool ContainsKey(TKey key);

        new bool TryGetValue(TKey key, out TValue value);

        IReadOnlyDictionaryData<TKey, TValue> AsReadOnly();
    }

    [Implement(typeof(IDictionaryDataEventDispatcher<,>))]
    [Implement(typeof(IReadOnlyDictionaryData<,>))]
    [Implement(typeof(IReadOnlyDictionary<,>))]
    [Implement(typeof(IDictionaryData<,>))]
    [Implement(typeof(IDictionary<,>))]
    internal sealed class KHBD_DictionaryData<TKey, TValue> : KHBD_Object, IDictionaryData<TKey, TValue> {

        public KHBD_DictionaryData() : base() => m_This = this;

        private IDictionaryData<TKey, TValue> m_This;
        private IDictionary<TKey, TValue> m_Dictionary = new Dictionary<TKey, TValue>();

        public event IChangable.ChangedDelegate Changed;
        public event IDictionaryData<TKey, TValue>.ClearedDelegate Cleared;
        public event IDictionaryData<TKey, TValue>.AddedDelegate Added;
        public event IDictionaryData<TKey, TValue>.UpdatedDelegate Updated;
        public event IDictionaryData<TKey, TValue>.RemovedDelegate Removed;

        int ICollection<KeyValuePair<TKey, TValue>>.Count => m_Dictionary.Count;
        int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => m_Dictionary.Count;
        int IDictionaryData<TKey, TValue>.Count => m_Dictionary.Count;

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => m_Dictionary.IsReadOnly;

        ICollection<TKey> IDictionary<TKey, TValue>.Keys => m_Dictionary.Keys;
        IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => m_Dictionary.Keys;
        ICollection<TKey> IDictionaryData<TKey, TValue>.Keys => m_Dictionary.Keys;

        ICollection<TValue> IDictionary<TKey, TValue>.Values => m_Dictionary.Values;
        IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => m_Dictionary.Values;
        ICollection<TValue> IDictionaryData<TKey, TValue>.Values => m_Dictionary.Values;

        TValue IReadOnlyDictionary<TKey, TValue>.this[TKey key] => m_This[key];
        TValue IDictionary<TKey, TValue>.this[TKey key] { get => m_This[key]; set => m_This[key] = value; }
        TValue IDictionaryData<TKey, TValue>.this[TKey key] {
            get => m_This.GetValue(key);
            set {
                if (!m_This.TryAdd(key, value)) {
                    m_This.Update(key, value);
                }
            }
        }

        bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => m_Dictionary.ContainsKey(key);
        bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => m_Dictionary.ContainsKey(key);
        bool IDictionaryData<TKey, TValue>.ContainsKey(TKey key) => m_Dictionary.ContainsKey(key);

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => m_Dictionary.Contains(item);

        IEnumerator IEnumerable.GetEnumerator() => m_This.GetEnumerator();

        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => m_Dictionary.GetEnumerator();

        bool IDictionaryData<TKey, TValue>.TryAdd(KeyValuePair<TKey, TValue> item) => m_This.TryAdd(item.Key, item.Value);

        bool IDictionaryData<TKey, TValue>.TryAdd(TKey key, TValue value) {
            if (m_This.ContainsKey(key)) {
                return false;
            } else {
                DoAdd(key, value);
                return true;
            }
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
            m_This.Add(item.Key, item.Value);
        }

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
            Assert.IsFalse(m_This.ContainsKey(key));
            DoAdd(key, value);
        }

        private void DoAdd(TKey key, TValue value) {
            m_Dictionary.Add(key, value);
            if (!KHBD.Context.IsApplicationQuit) {
                Added?.Invoke(key, value);
            }
            Dirty();
        }

        void IFixedLengthDictionaryData<TKey, TValue>.Update(TKey key, TValue value) {
            Assert.IsTrue(m_This.ContainsKey(key));

            var oldValue = m_Dictionary[key];
            m_Dictionary[key] = value;
            if (!KHBD.Context.IsApplicationQuit) {
                Updated?.Invoke(key, oldValue, value);
            }
            Dirty();
        }

        void IFixedLengthDictionaryData<TKey, TValue>.Update(TKey key, Action<TValue> updater) {
            Assert.IsNotNull(updater);
            Assert.IsTrue(m_This.ContainsKey(key));

            var value = m_Dictionary[key];
            updater(value);
            if (!KHBD.Context.IsApplicationQuit) {
                Updated?.Invoke(key, value, value);
            }
            Dirty();
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
            return m_This.Remove(item.Key);
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key) {
            if (m_This.TryGetValue(key, out var oldValue)) {
                m_Dictionary.Remove(key);
                if (!KHBD.Context.IsApplicationQuit) {
                    Removed?.Invoke(key, oldValue);
                }
                Dirty();
                return true;
            }
            return false;
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
            m_Dictionary.Clear();
            if (!KHBD.Context.IsApplicationQuit) {
                Cleared?.Invoke();
            }
            Dirty();
        }

        private void Dirty() {
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            Changed?.Invoke();
        }

        TValue IReadOnlyDictionaryData<TKey, TValue>.GetValue(TKey key) => m_Dictionary[key];

        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => m_Dictionary.TryGetValue(key, out value);
        bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => m_Dictionary.TryGetValue(key, out value);
        bool IDictionaryData<TKey, TValue>.TryGetValue(TKey key, out TValue value) => m_Dictionary.TryGetValue(key, out value);

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => m_Dictionary.CopyTo(array, arrayIndex);

        IDictionaryData<TKey, TValue> IReadOnlyDictionaryData<TKey, TValue>.AsReadWrite() => this;

        IReadOnlyDictionaryData<TKey, TValue> IDictionaryData<TKey, TValue>.AsReadOnly() => this;

        protected override void Dispose() {
            if (Cleared != null) {
                Cleared.Clear();
                Cleared = null;
            }
            if (Added != null) {
                Added.Clear();
                Added = null;
            }
            if (Removed != null) {
                Removed.Clear();
                Removed = null;
            }
            if (Updated != null) {
                Updated.Clear();
                Updated = null;
            }
            if (Changed != null) {
                Changed.Clear();
                Changed = null;
            }
            m_Dictionary = null;
            m_This = null;
        }
    }
}